package com.wuxuan.algorithm_homework.dfsAndBfs;

import org.junit.jupiter.api.Test;

import java.util.LinkedList;
import java.util.Queue;

/**
 * 广度优先搜索BFS
 */
public class BFS {

    private final int[][] directions = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};


    /**
     * 给定一个由'1'(陆地)和'0'(水)组成的二维网格地图，计算岛屿的数量。
     * 岛屿被水包围，并且通过水平或垂直方向连接相邻的陆地形成。你可以假设网格的四个边缘都被水包围。
     * <p>
     * m, n 为网格的行列
     * 时间复杂度: O(mn)
     * 空间复杂度: O(mn)
     *
     * @param grid
     * @return
     */
    public int numIsLands(char[][] grid) {
        if (grid == null || grid.length == 0) {
            return 0;
        }

        int m = grid.length;
        int n = grid[0].length;
        int count = 0;
        boolean[][] isVisited = new boolean[m][n];
        Queue<int[]> queue = new LinkedList<>();

        //遍历每一个网格节点
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                //如果当前节点还没有被搜索过，并且当前节点是1
                if (!isVisited[i][j] && grid[i][j] == '1') {
                    //将当前节点加入队列中
                    queue.offer(new int[]{i, j});
                    //标记当前节点已被搜索
                    isVisited[i][j] = true;
                    //标记岛屿+1
                    count++;
                    //查看队列queue
                    while (!queue.isEmpty()) {
                        //如果队列中非空，则获取并移出最先进队列的节点
                        int[] pos = queue.poll();
                        //对此节点的附近进一步进行搜索
                        for (int[] direction : directions) {
                            int x = pos[0] + direction[0];
                            int y = pos[1] + direction[1];
                            if (x < 0 || x >= m || y < 0 || y >= n || isVisited[x][y] || grid[x][y] == '0') {
                                //排除一些不需要搜索的
                                continue;
                            }
                            //需要搜索的,加入队列
                            queue.offer(new int[]{x, y});
                            isVisited[x][y] = true;
                        }
                    }
                }
            }
        }

        return count;
    }

    /**
     * 岛屿问题，被X包住的O全部改成X
     *
     * @param board
     */
    public void replaceSurroundedO(char[][] board) {
        if (board == null || board.length == 0) {
            return;
        }

        int m = board.length;
        int n = board[0].length;
        Queue<int[]> queue = new LinkedList<>();

        //遍历网格中每一个节点
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if ((i == 0 || j == 0 || i == m - 1 || j == n - 1) && board[i][j] == 'O') {
                    //如果当前节点是边界且是O,进行dfs,加入队列中
                    queue.offer(new int[]{i, j});
                    board[i][j] = '*';
                    //查看队列
                    while (!queue.isEmpty()) {
                        //获取并弹出队列中最先进的元素
                        int[] pos = queue.poll();
                        //获取其周围元素的情况
                        for (int[] direction : directions) {
                            int x = pos[0] + direction[0];
                            int y = pos[1] + direction[1];
                            if (x < 0 || x >= m || y < 0 || y >= n || board[x][y] != 'O') {
                                continue;
                            }
                            //如果有效，为O，则加入队列
                            queue.offer(new int[]{x, y});
                            board[x][y] = '*';
                        }
                    }
                }
            }
        }

        //执行完 BFS 后， 将所有未标记的 O 替换为 X，将标记过的 O 恢复为 O
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (board[i][j] == 'O') {
                    board[i][j] = 'X';
                } else if (board[i][j] == '*') {
                    board[i][j] = 'O';
                }
            }
        }
    }

    private void dfs(char[][] board, int i, int j, int m, int n, boolean[][] isVisited) {
        if (i < 0 || i >= m || j < 0 || j >= n || isVisited[i][j] || board[i][j] != 'O') {
            return;
        }
        //当前节点是要搜索的！加入队列

        isVisited[i][j] = true;

    }

    @Test
    public void testNumIsLands() {
        //示例 1:
        //输入:
        //11110
        //11010
        //11000
        //00000
        //输出:1
        char[][] grid = new char[][]{
                {'1', '1', '1', '1', '0'},
                {'1', '1', '0', '1', '0'},
                {'1', '1', '0', '0', '0'},
                {'0', '0', '0', '0', '0'},
        };
        System.out.println(numIsLands(grid));

        //示例 2:
        //输入:
        //11000
        //11000
        //00100
        //00011
        //输出:3
        char[][] grid2 = new char[][]{
                {'1', '1', '0', '0', '0'},
                {'1', '1', '0', '0', '0'},
                {'0', '0', '1', '0', '0'},
                {'0', '0', '0', '1', '1'},
        };
        System.out.println(numIsLands(grid2));
    }


    @Test
    public void testReplaceSurroundedO() {

        //示例 1:
        //2输入:
        char[][] board = new char[][]{
                {'X', 'X', 'X', 'X'},
                {'X', 'O', 'O', 'X'},
                {'X', 'X', 'O', 'X'},
                {'X', 'O', 'X', 'X'}
        };
        //输出:
//        board = new char[][]{
//                {'X', 'X', 'X', 'X'},
//                {'X', 'X', 'X', 'X'},
//                {'X', 'X', 'X', 'X'},
//                {'X', 'O', 'X', 'X'}
//        };
        replaceSurroundedO(board);
        for (char[] chars : board) {
            for (char c : chars) {
                System.out.print(c + " ");
            }
            System.out.println();
        }
    }
}
